package tv.geir.slackspace.client;

import java.io.Serializable;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import java.util.List;

import net.jini.core.entry.Entry;
import net.jini.core.entry.UnusableEntryException;
import net.jini.core.event.EventRegistration;
import net.jini.core.event.RemoteEventListener;
import net.jini.core.lease.Lease;
import net.jini.core.transaction.Transaction;
import net.jini.core.transaction.TransactionException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tv.geir.slackspace.analyzer.BytecodeDependencyAnalyzer;
import tv.geir.slackspace.code.BytecodeAgent;
import tv.geir.slackspace.code.PackageConfiguration;
import tv.geir.slackspace.code.ProminentClassLoader;
import tv.geir.slackspace.common.ProminentObject;
import tv.geir.slackspace.common.SlackLease;
import tv.geir.slackspace.common.SlackSpace;
import tv.geir.slackspace.common.SlackSpaceException;
import tv.geir.slackspace.communication.Request;
import tv.geir.slackspace.communication.Response;
import tv.geir.slackspace.communication.Request.Code;
import tv.geir.slackspace.matching.Template;
import tv.geir.slackspace.server.ServerLease;
/**
 * @author Geir Gullestad Pettersen
 *
 */
public class RemoteSlackSpace implements SlackSpace {
	/**
	 * Logger for this class
	 */
	private static Log log = LogFactory.getLog(RemoteSlackSpace.class);

	/**
	 * Used to send requests to the space
	 */
	private SlackClientConnector connector;
	
	/**
	 * Used to find class dependencies
	 */
	private BytecodeDependencyAnalyzer classAnalyzer;
	
	/**
	 * configuration
	 */
	private PackageConfiguration config; 
	
	private ProminentClassLoader cl;
	
	/*
	public RemoteSlackSpace( String address ) throws UnknownHostException {
		this( InetAddress.getByName( address ) , DEFAULT_PORT );
	}
	
	/**
	 * @param address
	 * @param port
	 *
	public RemoteSlackSpace( InetAddress address, int port ) {		
		setConnector( new SlackClientSocketConnector( address, port ));
	}
	*/
	
	/**
	 * Creates an instance of RemoteSlackSpace, you have to call
	 * setConnector() to connect to a SlackSpace.
	 * 
	 */
	public RemoteSlackSpace( SlackClientConnector connector ) {
		this.connector = connector;
	}
	
	public void connect( String url ) {
		if(log.isInfoEnabled()) {
			log.info("Connecting to SlackSpace: " + url);
		}
		connector.connect( url );
	}
	
	/**
	 * Sets the connector used to connect to a SlackSpace.
	 * @param connector
	 */
	public void setConnector( SlackClientConnector connector ) {
		this.connector = connector;
		classAnalyzer = new BytecodeDependencyAnalyzer();
		
		cl = new ProminentClassLoader();
		config = PackageConfiguration.getInstance();
	}
	

	/**
	 * Executes a raw request.
	 * 
	 * @param request The request to be executed on the space.
	 * @return A <code>tv.geir.slackspace.core.Response</code> representing the outcome of the execution. 
	 * @throws <code>tv.geir.slackspace.core.SlackSpaceException</code> if any communication error occurs or execution fails.
	 * Execution may fail if the request is unknown or invalid to the SlackSpace
	 */
	public Response executeRaw( Request request ) throws SlackSpaceException {
		return connector.execute( request );
	}
	
	/* (non-Javadoc)
	 * @see tv.geir.slackspace.SlackSpace#write(java.io.Serializable, net.jini.core.transaction.Transaction, long)
	 */
	public SlackLease write(Serializable entry, Transaction txn, long leaseTime) throws RemoteException, TransactionException {
		if( leaseTime < 0 ) {
			throw new IllegalArgumentException( "Leasetime must be equal to or grater than 0: \'" + leaseTime + "\'" );
		}
		try {
			
			// wrap object and attach dependencies
			ProminentObject po = new ProminentObject( entry );	
			po.addClassDefinitions( classAnalyzer.findDependencies( entry.getClass().getName() )); 
			
			System.out.println();
			
			Request request = new Request( po, Code.WRITE, leaseTime );	
			Response res = connector.execute( request );
			
			// creates a new lease from the response and passes a reference to
			// this instance of slackspace so the lease can perform operations on the space
			ClientLease lease = new ClientLease(this, ( ServerLease ) res.getData());			
			return lease;
		} catch (Throwable e) {
			log.error( "Error executing write: " + e.getMessage(), e );
			throw new RemoteException( "Cannot execute write: " + e.getMessage(), e );
		}
	}
		
	/* (non-Javadoc)
	 * @see net.jini.space.JavaSpace#write(net.jini.core.entry.Entry, net.jini.core.transaction.Transaction, long)
	 */
	public Lease write(Entry entry, Transaction txn, long leaseTime) throws TransactionException, RemoteException {		
		return write( (Serializable) entry, txn, leaseTime );
	}

	/**
	 * Executes a read or take operation on the SlackSpace.
	 * @param t
	 * @param txn
	 * @param timeout
	 * @param req
	 * @return
	 * @throws UnusableEntryException
	 * @throws TransactionException
	 * @throws InterruptedException
	 * @throws RemoteException
	 */
	private Serializable readOrTake( Template t, Transaction txn, long timeout, Code req ) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		if( txn != null ){
			log.warn( "Transaction was not null. Transactions are not yet implemented" );
		}
		if( timeout < 0 ) {
			throw new IllegalArgumentException( "Timeout must be equal to or grater than 0: \'" + timeout + "\'" );
		} else if( req != Code.READ &&
				   req != Code.READ_IF_EXISTS &&
				   req != Code.TAKE && 
				   req != Code.TAKE_IF_EXISTS ) {
			throw new IllegalArgumentException( "Request must be of type read, read_on_exists, take or tak_if_exists" );
		}
		try {
			
			// send read command			
			Request request = new Request( t, req, timeout );	
			Response res = connector.execute( request );
			
			// read response
			Serializable s = ( Serializable ) res.getData();
			if( s != null && s instanceof ProminentObject ) {
				((ProminentObject) s ).setClassLoader( cl );
				return ((ProminentObject) s ).deSerialize();
			}
			return s;
		} catch (Throwable e) {
			log.error( "Error executing " + req.toString() + ": " + e.getMessage(), e );
			throw new RemoteException( "Cannot execute read", e );
		}
	} 
	
	/**
	 * Read any matching entry from the space, blocking until one exists. Return null if
	 * timeout expires.
	 * 
	 * @param t the template used to match objects in the space
	 * @param txn The transaction (if any) under which to work
	 * @param timeout How long the client is willing to wait for a proper matching entry
	 * @return a copy of the entry read from the space
	 * @throws UnusableEntryException
	 * @throws TransactionException
	 * @throws InterruptedException
	 * @throws RemoteException
	 */
	public Serializable read( Template t, Transaction txn, long timeout ) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		return readOrTake( t, txn, timeout, Code.READ );
	}
	
	public Serializable take(Template t, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		return readOrTake( t, txn, timeout, Code.TAKE );
	}


	public Serializable readIfExists(Template t, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {		
		return readOrTake( t, txn, timeout, Code.READ_IF_EXISTS );
	}

	public Serializable takeIfExists(Template t, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {		
		return readOrTake( t, txn, timeout, Code.TAKE_IF_EXISTS );
	}
	
	/* (non-Javadoc)
	 * @see net.jini.space.JavaSpace#read(net.jini.core.entry.Entry, net.jini.core.transaction.Transaction, long)
	 */
	public Entry read(Entry entry, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		Template t = new Template( entry, false );
		return ( Entry ) read( t, txn, timeout );
	}

	public Entry readIfExists(Entry entry, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		Template t = new Template( entry, false );
		return (Entry )readIfExists( t, txn, timeout );
	}
	
	public Entry take(Entry entry, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		Template t = new Template( entry, false );
		return ( Entry ) take( t, txn, timeout );
	}

	public Entry takeIfExists(Entry entry, Transaction txn, long timeout) throws UnusableEntryException, TransactionException, InterruptedException, RemoteException {
		Template t = new Template( entry, false );
		return ( Entry ) takeIfExists( t, txn, timeout );
	}

	public EventRegistration notify(Entry entry, Transaction txn, RemoteEventListener arg2, long timeout, MarshalledObject mo) throws TransactionException, RemoteException {
		return null;
	}

	public Entry snapshot(Entry entry) throws RemoteException {
		return null;
	}

	public void disconnect() {
		
		connector.disconnect();
	}


	
	static { 
		if( BytecodeAgent.isLoaded() ) {
			log.info( "BytecodeAgent is loaded" );
		} else {
			log.error( "BytecodeAgent NOT loaded. This will disable complete bytecode analysis used for class distribution across spaces and clients" );
			log.error( "Must be startet with JVM argument:");
			log.error( " -javaagent:<path to agent jar-file>");
			log.error( "where path is the location of tv-geir-slackspaces-VERSION.jar");
		}
	}



	public List<Serializable> readAll(Template t) {
		return null;
	}


	public List<Serializable> takeAll(Template t) {
		return null;
	}

	
	public void addPackage(String packageName) {
		
		config.addAllowedPackage(packageName);
	}

	public void setClassAnalyzer(BytecodeDependencyAnalyzer classAnalyzer) {
		this.classAnalyzer = classAnalyzer;
	}
	

	
}
